using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Arcadium.Graphics;

namespace Arcadium.GameEntities
{
    class Map : GameEntity
    {
        struct SpawnArea
        {
            public Vector2 StartPoint { get; set; }
            public Vector2 AreaSpan { get; set; }

            public SpawnArea(Vector2 startPoint, Vector2 areaSpan) : this()
            {
                this.StartPoint = startPoint;
                this.AreaSpan = areaSpan;
            }
        }

        #region Field

        private int collisionDataRowWidth;
        private Color[] collisionData;

        private List<Texture2D> backgroundTexture;
        private List<Texture2D> foregroundTexture;

        private Texture2D maskTexture;

        private List<SpawnArea> spawnAreas;
        private List<Vector2> pointOfInterests;

        #endregion

        #region Initialization

        public Map(World world, Texture2D maskTexture, List<Texture2D> backgroundTexture, 
            List<Texture2D> foregroundTexture) : this(world)
        {
            SetMask(maskTexture);
            this.backgroundTexture = backgroundTexture;
            this.foregroundTexture = foregroundTexture;
        }

        public Map(World world) : base(world)
        {
            this.backgroundTexture = new List<Texture2D>();
            this.foregroundTexture = new List<Texture2D>();
            this.spawnAreas = new List<SpawnArea>();
            this.pointOfInterests = new List<Vector2>();
        }

        public void SetMask(Texture2D maskTexture)
        {
            collisionDataRowWidth = maskTexture.Width;
            collisionData = new Color[maskTexture.Width * maskTexture.Height];
            maskTexture.GetData<Color>(collisionData);
            //maskTexture.Dispose();

            this.maskTexture = maskTexture;
        }

        public void AddBackground(Texture2D texture)
        {
            this.backgroundTexture.Add(texture);
        }

        public void AddForeground(Texture2D texture)
        {
            this.foregroundTexture.Add(texture);
        }

        public void AddSpawnArea(Vector2 startPoint, Vector2 areaSpan)
        {
            this.spawnAreas.Add(new SpawnArea(startPoint, areaSpan));
        }

        public void AddPOI(params Vector2[] pointOfInterest)
        {
            foreach (Vector2 point in pointOfInterest)
                this.pointOfInterests.Add(point);
        }

        #endregion

        #region Update and Draw

        public Vector2 GetRandomSpawnPoint(ref int previousSpawnAreaIndex, float objectWidth)
        {
            int spawnAreaIndex = 0;
            while ((spawnAreaIndex = World.Random.Next(spawnAreas.Count)) == previousSpawnAreaIndex) ;
            SpawnArea spawnArea = spawnAreas[(previousSpawnAreaIndex = spawnAreaIndex)];
            return spawnArea.StartPoint + Vector2.Multiply(spawnArea.AreaSpan, 
                objectWidth / (2 * spawnArea.AreaSpan.Length()) +
                (1 - objectWidth / spawnArea.AreaSpan.Length()) * (float)World.Random.NextDouble());
        }

        public Vector2 GetPlayerSpawnPoint(int prio)
        {
            return pointOfInterests[prio];
        }

        public Vector2 GetEnemySpawnPoint()
        {
            return pointOfInterests[4];
        }

        public override void Draw(SpriteBatch spriteBatch)
        {
            foreach (Texture2D texture in backgroundTexture)
            {
                spriteBatch.Draw(texture, View.ScreenRect, null,
                    Color.White, 0, Vector2.Zero, SpriteEffects.None, 0);
            }
        }

        #endregion

        #region Collision

        /// <summary>
        /// Enum used by collision check.
        /// </summary>
        private enum Side { Up, Right, Down, Left }
        /// <summary>
        /// Perform collision for an object at the given position width the given velocity.
        /// </summary>
        public void CheckCollision(GameTime gameTime, 
            ref Vector2 position, ref Vector2 velocity, 
            ref bool OnGround, Vector2 origin)
        {
            {
                position += new Vector2(0, (float)gameTime.ElapsedGameTime.TotalMilliseconds * velocity.Y);
                int direction = velocity.Y > 0 ? -1 : 1;
                Side side = velocity.Y > 0 ? Side.Down : Side.Up;
                bool hit = false;
                while (CheckCollision(position, side, origin))
                {
                    if (!hit)
                    {
                        velocity.Y = 0;
                        if (side == Side.Down)
                            OnGround = true;
                        hit = true;
                    }
                    position.Y += direction;
                }
            }
            {
                position += new Vector2((float)gameTime.ElapsedGameTime.TotalMilliseconds * velocity.X, 0);
                int direction = velocity.X > 0 ? -1 : 1;
                Side side = velocity.X > 0 ? Side.Right : Side.Left;
                bool hit = false;
                while (CheckCollision(position, side, origin))
                {
                    if (!hit)
                    {
                        velocity.X = 0;
                        hit = true;
                    }
                    position.X += direction;
                }
            }
        }
        private bool CheckCollision(Vector2 position, Side side, Vector2 origin)
        {
            if (side == Side.Up || side == Side.Down)
            {
                int y = (int)Math.Round(side == Side.Up ? position.Y - origin.Y : position.Y + origin.Y);
                for (int x = (int)Math.Round(position.X - origin.X); x < position.X + origin.X; x++)
                    if (World.Map.CheckCollision(new Vector2(x, y)))
                        return true;
            }
            else if (side == Side.Left || side == Side.Right)
            {
                int x = (int)Math.Round(side == Side.Left ? position.X - origin.X : position.X + origin.X);
                for (int y = (int)Math.Round(position.Y - origin.Y); y < position.Y + origin.Y; y++)
                    if (CheckCollision(new Vector2(x, y)))
                        return true;
            }
            return false;
        }

        /// <summary>
        /// Checks collision for the given position.
        /// </summary>
        /// <param name="position"></param>
        /// <returns>True if this position is solid ground.</returns>
        public bool CheckCollision(Vector2 position)
        {
            int index = (int)Math.Round(position.Y) * collisionDataRowWidth + (int)Math.Round(position.X);
            if (index < 0 || index > collisionData.Length)
                return false;
            return collisionData[index].R < 127;
        }

        /// <summary>
        /// Checks collision for the given position.
        /// </summary>
        /// <param name="position"></param>
        /// <returns>True if this position is solid ground.</returns>
        public bool CheckCollision(Rectangle rectangle)
        {
            for (int x = rectangle.Left; x < rectangle.Right; x++)
                for (int y = rectangle.Top; y < rectangle.Bottom; y++)
                {
                    int index = y * collisionDataRowWidth + x;
                    if (index >= 0 && index < collisionData.Length)
                    {
                        if (collisionData[index].R < 127)
                            return true;
                    }
                }
            return false;
        }

        #endregion

    }
}